/*
 * Copyright  bocmbci
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import webview from '@ohos.web.webview';
import { PluginResult, PluginResultStatus } from './PluginResult';
import LinkedList from '@ohos.util.LinkedList';
import { StringBuilder } from './StringBuilder';

export class NativeToJsMessageQueue{
  private static MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
  // Set this to true to force plugin results to be encoding as
  // JS instead of the custom format (useful for benchmarking).
  // Doesn't work for multipart messages.
  private static FORCE_ENCODE_USING_EVAL = false;

  // Disable sending back native->JS messages during an exec() when the active
  // exec() is asynchronous. Set this to true when running bridge benchmarks.
  public static DISABLE_EXEC_CHAINING = true;

  private paused = false;

  private queue: LinkedList<JsMessage> = new LinkedList();

  private bridgeModes: LinkedList<BridgeMode> = new LinkedList();

  private activeBridgeMode: BridgeMode;

  constructor(webviewController: WebviewController) {
    this.addBridgeMode(new EvalBridgeMode(webviewController));
  }

  private  addBridgeMode(bridgeMode: BridgeMode) {
    this.bridgeModes.add(bridgeMode);
  }

  public isBridgeEnabled(): boolean {
    return this.activeBridgeMode !== null;
  }

  public isEmpty(): boolean {
    return this.queue.length === 0;
  }

  public setBridgeMode(value: number): void {
    if (value < -1 ) {
      console.log("Invalid NativeToJsBridgeMode: " + value);
    } else {
      //todo 目前只有一种birdgemode
      if(value > 0){
        value = 0;
      }
      const newMode = value < 0 ? null : this.bridgeModes[value];
      if (newMode !== this.activeBridgeMode) {
        this.activeBridgeMode = newMode;
        if (newMode !== null) {
          newMode.reset();
          if (!this.paused && this.queue.length > 0) {
            newMode.onNativeToJsMessageAvailable(this);
          }
        }
      }
    }
  }

  public reset(): void {
    this.queue.clear();
    this.setBridgeMode(-1);
  }


  private packMessage(message: JsMessage, sb: StringBuilder): void {
    sb.append(' ');
    message.encodeAsMessage(sb);
  }

  public popAndEncode(fromOnlineEvent: boolean): string | null {
    if (this.activeBridgeMode === null) {
      return null;
    }
    this.activeBridgeMode.notifyOfFlush(this, fromOnlineEvent);
    if (this.queue.length === 0) {
      return null;
    }
    let totalPayloadLen = 0;
    let numMessagesToSend = 0;
    for (const message of this.queue) {
      numMessagesToSend ++;
    }

    const sb = new StringBuilder(totalPayloadLen + '');
    for (let i = 0; i < numMessagesToSend; i++) {
      const message = this.queue.removeFirst();
      this.packMessage(message, sb);
    }

    if (this.queue.length > 0) {
      // 在返回结果后附加一个字符以指示还有更多消息等待。
      sb.append('*');
    }
    const ret = sb.toString();
    return ret;
  }

  /**
   * Same as popAndEncode(), except encodes in a form that can be executed as JS.
   */
  public popAndEncodeAsJs(): string {
    let length = this.queue.length;
    if (length === 0) {
      return null;
    }
    let numMessagesToSend = 0;

    for (const message of this.queue) {
      numMessagesToSend += 1;
    }

    const willSendAllMessages = numMessagesToSend === length;
    // const sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100) + '');
    const sb = new StringBuilder('');

    for (let i = 0; i < numMessagesToSend; ++i) {
      const message = this.queue.removeFirst();
      if (willSendAllMessages && (i + 1 === numMessagesToSend)) {
        message.encodeAsJsMessage(sb);
      } else {
        sb.append('try{');
        message.encodeAsJsMessage(sb);
        sb.append('}finally{');
      }
    }

    if (!willSendAllMessages) {
      sb.append('window.setTimeout(function(){cordova.require(\'cordova/plugin/android/polling\').pollOnce();},0);');
    }

    for (let i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
      sb.append('}');
    }

    return sb.toString();
  }

  addJavaScript(statement: string){
    this.enqueueMessage(new JsMessage(null, statement))
  }

  public addPluginResult(result: PluginResult, callbackId: string): void {
    if (callbackId === null) {
      return;
    }
    // Don't send anything if there is no result and there is no need to clear the callbacks.
    const noResult = result.getStatus() === PluginResultStatus.NO_RESULT;
    const keepCallback = result.getKeepCallback();
    if (noResult && keepCallback) {
      return;
    }
    let message: JsMessage = new JsMessage(result, callbackId);
    if (NativeToJsMessageQueue.FORCE_ENCODE_USING_EVAL) {
      const sb = new StringBuilder('');
      message.encodeAsJsMessage(sb);
      message = new JsMessage(null, sb.toString());
    }

    this.enqueueMessage(message);
  }

  private enqueueMessage(message: JsMessage): void {
    if (this.activeBridgeMode === null) {
      console.log("Dropping Native->JS message due to disabled bridge");
    } else {
      this.queue.add(message);
      if(!this.paused){
        this.activeBridgeMode.onNativeToJsMessageAvailable(this)
      }
    }
  }

  public setPaused(value: boolean): void {
    if (this.paused && value) {
      console.error("nested call to setPaused detected.");
    }
    this.paused = value;
    if (!value) {
      if (this.queue.length > 0 && this.activeBridgeMode != null) {
        this.activeBridgeMode.onNativeToJsMessageAvailable(this);
      }
    }
  }
}

abstract class BridgeMode {
  abstract onNativeToJsMessageAvailable(queue: NativeToJsMessageQueue);
  notifyOfFlush(queue: NativeToJsMessageQueue , fromOnlineEvent: boolean) {}
  reset() {}
}

class EvalBridgeMode extends BridgeMode {
  webviewController: webview.WebviewController

  constructor(_webViewController) {
    super();
    this.webviewController = _webViewController;
  }

  onNativeToJsMessageAvailable(queue: NativeToJsMessageQueue) {
    let js = queue.popAndEncodeAsJs();
    if (js != null) {
      this.webviewController.runJavaScript(js)
    }
  }
}

class JsMessage {
  private jsPayloadOrCallbackId: string;
  private pluginResult?: PluginResult;

  constructor(pluginResult: PluginResult, callbackId: string) {
    this.jsPayloadOrCallbackId = callbackId;
    this.pluginResult = pluginResult;
  }


  encodeAsMessage(sb): void {
    if (this.pluginResult === null) {
      sb.append('J')
        .append(this.jsPayloadOrCallbackId);
      return;
    }
    const status = this.pluginResult.getStatus();
    const noResult = status === PluginResultStatus.NO_RESULT;
    const resultOk = status === PluginResultStatus.OK;
    const keepCallback = this.pluginResult.getKeepCallback();

    sb.append((noResult || resultOk) ? 'S' : 'F')
      .append(keepCallback ? '1' : '0')
      .append(status)
      .append(' ')
      .append(this.jsPayloadOrCallbackId)
      .append(' ');

    JsMessage.encodeAsMessageHelper(sb, this.pluginResult);
  }

  static  encodeAsMessageHelper(sb, pluginResult){
    if(pluginResult != null && pluginResult != undefined){
      sb.append(pluginResult.getMessage())
    }
  }

  buildJsMessage(sb): void {
    let message = this.pluginResult.getMessage();

    sb.append(JSON.stringify(message));
  }

  encodeAsJsMessage(sb): void {
    if (!this.pluginResult) {
      sb.append(this.jsPayloadOrCallbackId);
    } else {
      let status: number;
      status = this.pluginResult.getStatus();
      let success: boolean;
      success = (status === PluginResultStatus.OK) || (status === PluginResultStatus.NO_RESULT);
      sb.append("cordova.callbackFromNative('")
        .append(this.jsPayloadOrCallbackId)
        .append("',")
        .append(success.toString())
        .append(",")
        .append(status)
        .append(",[");
      this.buildJsMessage(sb);
      sb.append("],")
        .append(this.pluginResult.getKeepCallback().toString())
        .append(");");
    }
  }
}